package com.freeme.cameraplugin.qrcodescan;


import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.freeme.camera.BasePluginImpl;
import com.freeme.camera.CameraInterfaceManage;
import com.freeme.camera.ICameraExt;
import com.freeme.camera.data.PictureSizeInfo;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

import java.util.Hashtable;
import java.util.Vector;

public class QrCodeScan extends BasePluginImpl {
    private static final String TAG = "QrCodeScan";
    public static final String BARCODE_BITMAP = "barcode_bitmap";
    private static final int FREEME_QRCODESCAN_ID = 104;
    private static final int CLOSE_CAMERA_MODE_CMD = 0x010;
    private String mPkgName;
    private LayoutInflater mLayoutInflater;
    private ViewGroup mRootView;
    private ViewGroup mQrcodeViewGroup;
    private ICameraExt mCameraExt;
    private HandlerThread mThread;
    private DecodeHandler mDecodeHandler;
    private Camera mCamera;
    private CameraConfigManager mCameraConfigManager;
    private Rect framingRectInPreview;
    public static final int MSG_DECODE_SUCCEED = 0;
    public static final int MSG_DECODE_FAIL = 1;
    public static final int MSG_START_DECODE = 2;
    public static final int MSG_QUIT_DECODE = 3;
    public static final int MSG_SET_PREVIEW_CALLBACK = 4;
    private boolean mIsCoding;
    private static final int PREVIEW_CALLBACK_DELAY = 500;


    public static boolean startActivitySafely(Context context, Intent intent, Object tag) {
        try {
            context.startActivity(intent);
            return true;
        } catch (SecurityException e) {
            Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Launcher does not have the permission to launch " + intent
                    + ". Make sure to create a MAIN intent-filter for the corresponding activity "
                    + "or use the exported attribute for this activity. " + "tag=" + tag
                    + " intent=" + intent, e);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
        }
        return false;
    }

    private Handler mMainHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            Log.i(TAG, "mMainHandler message what : " + message.what);
            if (message.what == MSG_DECODE_SUCCEED) {
                Log.d(TAG, "Got decode succeeded message");
                Result result = (Result) message.obj;

                String url = result.getText();
                if (TextUtils.isEmpty(url)) {
                    return;
                }

                Log.i(TAG, "Got product query message url : " + url);
                boolean isValidUri = false;
                if (Patterns.WEB_URL.matcher(url).matches()) {
                    isValidUri = true;
                } else {
                    isValidUri = false;
                }
                if (isValidUri) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setData(Uri.parse(url));
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivitySafely(mContext, intent, url);
                } else {
                    showDialog(R.string.toast_valid_uri, url);
                }
            } else if (message.what == MSG_DECODE_FAIL) {
                mIsCoding = false;
            } else if (message.what == MSG_SET_PREVIEW_CALLBACK) {
                if (mCamera != null) {
                    mCameraConfigManager.initFromCameraParameters(mCamera);
                    mCamera.setPreviewCallback(mPreviewCallback);
                }
            }
        }
    };

    private class DecodeHandler extends Handler {
        private Hashtable<DecodeHintType, Object> mHints = new Hashtable<DecodeHintType, Object>();
        private MultiFormatReader multiFormatReader;

        public DecodeHandler(Looper looper, Vector<BarcodeFormat> decodeFormats, String characterSet) {
            super(looper);
            multiFormatReader = new MultiFormatReader();
            if (decodeFormats == null || decodeFormats.isEmpty()) {
                decodeFormats = new Vector<BarcodeFormat>();
                decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
                decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
                decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
            }

            mHints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);

            if (characterSet != null) {
                mHints.put(DecodeHintType.CHARACTER_SET, characterSet);
            }
            multiFormatReader.setHints(mHints);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == MSG_START_DECODE) {
                decode((byte[]) msg.obj, msg.arg1, msg.arg2);
            } else if (msg.what == MSG_QUIT_DECODE) {
                Looper.myLooper().quit();
            }
        }


        private void decode(byte[] data, int width, int height) {
            long start = System.currentTimeMillis();
            Result rawResult = null;
            Log.i(TAG, "decode bate length : " + data.length + ",width : " + width + ",height : " + height);
            //modify here
            byte[] rotatedData = new byte[data.length];
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++)
                    rotatedData[x * height + height - y - 1] = data[x + y * width];
            }
            int tmp = width; // Here we are swapping, that's the difference to #11
            width = height;
            height = tmp;

            PlanarYUVLuminanceSource source = buildLuminanceSource(rotatedData, width, height);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            try {
                rawResult = multiFormatReader.decodeWithState(bitmap);
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }

            if (rawResult != null) {
                long end = System.currentTimeMillis();
                Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
                Message message = Message.obtain(mMainHandler, MSG_DECODE_SUCCEED, rawResult);
                Bundle bundle = new Bundle();
                bundle.putParcelable(BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
                message.setData(bundle);
                //Log.d(TAG, "Sending decode succeeded message...");
                message.sendToTarget();
            } else {
                Message message = Message.obtain(mMainHandler, MSG_DECODE_FAIL);
                message.sendToTarget();
            }
        }
    }

    private void showDialog(int strId, String info) {
        String str = mContext.getResources().getString(strId);
        String confirm = mContext.getResources().getString(R.string.info_confirm);
        new AlertDialog.Builder(mCameraExt.getHostContext()).setTitle(str).setMessage(info).setPositiveButton(confirm,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        mIsCoding = false;
                    }
                }).setCancelable(false).show();
    }

    public Rect getFramingRectInPreview() {
        if (framingRectInPreview == null) {
            Rect rect = new Rect(QrCodeScanView.sFramingRect);
            Point cameraResolution = mCameraConfigManager.getCameraResolution();
            Point screenResolution = mCameraConfigManager.getScreenResolution();
            rect.left = rect.left * cameraResolution.y / screenResolution.x;
            rect.right = rect.right * cameraResolution.y / screenResolution.x;
            rect.top = rect.top * cameraResolution.x / screenResolution.y;
            rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
            framingRectInPreview = rect;
        }
        return framingRectInPreview;
    }

    public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
        Rect rect = getFramingRectInPreview();
        Log.i(QrCodeScan.TAG, "buildLuminanceSource rect:" + rect.toString());
        int previewFormat = mCameraConfigManager.getPreviewFormat();
        Log.i(QrCodeScan.TAG, "buildLuminanceSource previewFormat : " + previewFormat);
        String previewFormatString = mCameraConfigManager.getPreviewFormatString();
        Log.i(QrCodeScan.TAG, "buildLuminanceSource previewFormatString : " + previewFormatString);
        switch (previewFormat) {
            // This is the standard Android format which all devices are REQUIRED to support.
            // In theory, it's the only one we should ever care about.
            case PixelFormat.YCbCr_420_SP:
                // This format has never been seen in the wild, but is compatible as we only care
                // about the Y channel, so allow it.
            case PixelFormat.YCbCr_422_SP:
                return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
                        rect.width(), rect.height());
            default:
                // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
                // Fortunately, it too has all the Y data up front, so we can read it.
                if ("yuv420p".equals(previewFormatString)) {
                    return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
                            rect.width(), rect.height());
                }
        }
        throw new IllegalArgumentException("Unsupported picture format: " +
                previewFormat + '/' + previewFormatString);
    }

    public QrCodeScan() {
        super();
    }

    public QrCodeScan(Context context, String pkgName) {
        super(context, pkgName);
        mPkgName = pkgName;
        mLayoutInflater = LayoutInflater.from(mContext);
        mLayoutInflater.setFactory(new LayoutInflater.Factory() {
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                if (name.equals("com.freeme.cameraplugin.qrcodescan.QrCodeScanView")) {
                    return new QrCodeScanView(context, attrs);
                }
                return null;
            }
        });
        mCameraConfigManager = new CameraConfigManager(context);
    }

    private void showQr() {
        if (mQrcodeViewGroup == null) {
            mQrcodeViewGroup = (ViewGroup) mLayoutInflater.inflate(R.layout.activity_qrcode_scan, null);
            mQrcodeViewGroup.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            });
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.MATCH_PARENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT);
            lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
            mRootView.addView(mQrcodeViewGroup, lp);
        }
    }

    private void hideQr() {
        if (mQrcodeViewGroup == null || mRootView == null) {
            return;
        }
        mRootView.removeView(mQrcodeViewGroup);
        mQrcodeViewGroup = null;
    }

    private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.i(TAG, "onPreviewFrame data length : " + data.length + ",current thread ; " + Thread.currentThread().getName()
                    + ",mIsCoding : " + mIsCoding);
            if (QrCodeScanView.sFramingRect == null) {
                return;
            }
            if (mDecodeHandler != null && !mIsCoding) {
                mIsCoding = true;
                Point cameraResolution = mCameraConfigManager.getCameraResolution();
                Message message = mDecodeHandler.obtainMessage(MSG_START_DECODE, cameraResolution.x, cameraResolution.y, data);
                message.sendToTarget();
            } else {
                Log.d(TAG, "Got preview callback, but no handler for it");
            }
        }
    };

    @Override
    public int getModuleID() {
        return FREEME_QRCODESCAN_ID;
    }

    @Override
    public String getPkgName() {
        return mPkgName;
    }

    @Override
    public String getModuleTitle() {
        return mContext.getResources().getString(R.string.app_name);
    }

    @Override
    public Drawable getModuleIcon() {
        return mContext.getDrawable(R.drawable.freeme_scene_qrcode);
    }

    @Override
    public PictureSizeInfo getPictureSizeInfo() {
        PictureSizeInfo info = new PictureSizeInfo(0, 0);
        info.setExpectOutImageSize(2 * 1024 * 1024);
        info.setExpectScreenRatio(16d / 9);
        return info;
    }

    @Override
    public void showPanel(ViewGroup root) {
        if (mCameraExt == null) {
            mCameraExt = CameraInterfaceManage.getInstance().cameraExt;
        }
        mCamera = mCameraExt.getApi1Camera();
        Log.i(TAG, "showPanel mCamera:" + mCamera);
        if (mCamera == null) {
            return;
        }
        mRootView = root;
        mThread = new HandlerThread("decode-thread");
        mThread.start();
        mDecodeHandler = new DecodeHandler(mThread.getLooper(), null, null);
        showQr();
        mCameraExt.setBottomBarVisible(View.GONE);
        mCameraExt.setTopBarVisible(View.GONE);
        mMainHandler.sendEmptyMessageDelayed(MSG_SET_PREVIEW_CALLBACK, PREVIEW_CALLBACK_DELAY);
    }

    @Override
    public void hidePanel() {
        Log.i(TAG, "hidePaneal");
        hideQr();
        mCameraExt.setBottomBarVisible(View.VISIBLE);
        mCameraExt.setTopBarVisible(View.VISIBLE);
        if (QrCodeScanView.sFramingRect != null) {
            QrCodeScanView.sFramingRect = null;
        }
        if (mDecodeHandler != null) {
            mDecodeHandler = null;
        }
        if (mThread != null) {
            mThread.quitSafely();
            mThread = null;
        }
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
        }
        if (mMainHandler != null) {
            mMainHandler.removeMessages(MSG_SET_PREVIEW_CALLBACK);
        }
        mIsCoding = false;
        mCameraExt = null;
    }

}
